#include <map>
#include <vector>
#include <nice2d/application.h>
#include <nice2d/components/quadtree.h>
#include <nice2d/components/group.h>
#include <nice2d/components/controllable.h>
#include <nice2d/quadtreescene.h>
#include <nice2d/animation.h>
#include <nice2d/spritedeletor.h>
#include <nice2d/spritecontroller.h>
#include <nice2d/components/spritestatus.h>
#include <iostream>

class SpriteVisitor;

class QuadTreeSceneData : public NodeVisitor
{
public:
    QuadTreeSceneData(QuadTreeScene &inputScene):
        scene(inputScene)
    {
        auto windowSize = Application::getInstance()->getWindow()->getSize();
        quadTree = std::make_shared<QuadTree>(sf::FloatRect(0.0f, 0.0f, windowSize.x, windowSize.y), 0, 5);
    }

    void visitNode(NodePointer object) override
    {
        auto sprite = std::dynamic_pointer_cast<Sprite>(object);
        if (sprite)
            quadTree->insert(sprite);
    }

    void setDeltaTime(float inputDeltaTime)
    {
        deltaTime = inputDeltaTime;
    }

    void updateSprite(NodePointer object)
    {
        if (!object)
            return;

        auto sprite = std::dynamic_pointer_cast<Sprite>(object);
        if (!sprite) {
            object->update(deltaTime);
            return;
        }

        auto controllable = GET_COMPONENT(sprite, Controllable);

        if (!controllable->isControllable())
            sprite->update(deltaTime);
        else {
            auto group = GET_COMPONENT(sprite, Group);
            auto find = spriteControllers.find(group->getGroup());
            if (find != spriteControllers.end()) {
                auto list = find->second;
                for (auto itr = list.begin(); itr != list.end(); itr++) {
                    auto spriteController = *itr;
                    spriteController->updateSprite(sprite, deltaTime);
                }
            } else
                sprite->update(deltaTime);
        }
    }

    bool shouldDeleteSprite(NodePointer object)const
    {
        if (!object)
            return false;

        auto sprite = std::dynamic_pointer_cast<Sprite>(object);
        if (!sprite)
            return false;

        auto group = GET_COMPONENT(sprite, Group);

        auto find = spriteDeleters.find(group->getGroup());
        if (find != spriteDeleters.end()) {
            auto list = find->second;
            for (auto itr = list.begin(); itr != list.end(); itr++) {
                auto spriteDeleter = *itr;
                if (spriteDeleter->shouldDelete(sprite))
                    return true;
            }
        }

        return false;
    }

    QuadTreeScene &scene;
    std::vector<int> conllisionPair;
    std::shared_ptr<QuadTree> quadTree;

    std::map<int, std::list<SpriteDeleterPointer>> spriteDeleters;
    std::map<int, std::list<SpriteControllerPointer>> spriteControllers;
    float deltaTime = 0.0f;
};

class SpriteSearchVisitor : public NodeVisitor
{
public:
    SpriteSearchVisitor(QuadTreeScene &inputScene):
        scene(inputScene)
    {
    }

    void visitNode(NodePointer object) override
    {
        auto sprite = std::dynamic_pointer_cast<Sprite>(object);
        if (!sprite || GET_COMPONENT(sprite, SpriteStatus)->getSpriteStatus() == SPRITE_STATUS_DEATH)
            return;

        auto groupID = GET_COMPONENT(sprite, Group)->getGroup();
        auto find = std::find(scene.data->conllisionPair.begin(), scene.data->conllisionPair.end(),
                              groupID);
        if (find == scene.data->conllisionPair.end())
            return;

        sf::FloatRect spriteBox = sprite->getBoundingBox();

        auto quadTree = scene.data.get()->quadTree;
        auto items = quadTree->search(spriteBox);
        if (items.size() > 1) {
            items.erase(sprite);

            auto itr = items.begin();
            while (itr != items.end()) {
                auto item = *itr;
                if (item->getSpriteOwner() == sprite ||
                        item == sprite->getSpriteOwner())
                    itr = items.erase(itr);
                if (itr != items.end())
                    itr ++;
            }

            for (auto &item : items) {
                auto itemBox = item->getBoundingBox();
                if (spriteBox.intersects(itemBox))
                    scene.onConllision(sprite, item);
            }
        }
    }
private:
    QuadTreeScene &scene;
};

QuadTreeScene::QuadTreeScene(const std::string &name):
    Scene(name),
    data(new QuadTreeSceneData(*this))
{
}

QuadTreeScene::~QuadTreeScene()
{
}

void QuadTreeScene::addSpriteDeleter(int groupID, SpriteDeleterPointer deleter)
{
    if (!deleter)
        return;

    deleter->setScene(std::dynamic_pointer_cast<Scene>(shared_from_this()));

    auto itr = data->spriteDeleters.find(groupID);
    if (itr != data->spriteDeleters.end())
        itr->second.push_back(deleter);
    else {
        auto group = groupID;
        auto node = std::make_pair<int, std::list<SpriteDeleterPointer>>(std::move(group), {deleter});
        data->spriteDeleters.insert(node);
    }
}

void QuadTreeScene::addSpriteController(int groupID, SpriteControllerPointer controller)
{
    if (!controller)
        return;

    controller->setScene(std::dynamic_pointer_cast<Scene>(shared_from_this()));

    auto itr = data->spriteControllers.find(groupID);
    if (itr != data->spriteControllers.end())
        itr->second.push_back(controller);
    else {
        auto group = groupID;
        auto node = std::make_pair<int, std::list<SpriteControllerPointer>>(std::move(group), {controller});
        data->spriteControllers.insert(node);
    }
}

void QuadTreeScene::onConllision(SpritePointer current, SpritePointer target)
{
    (void)current;
    (void)target;
}

void QuadTreeScene::onUpdateScene(float deltaTime)
{
    data->setDeltaTime(deltaTime);
    auto fn = std::bind(&QuadTreeSceneData::updateSprite, data.get(), std::placeholders::_1);
    visitChild(fn);

    data->quadTree->clear();

    acceptNode(data.get());

    SpriteSearchVisitor visitor(*this);
    acceptNode(&visitor);

    auto deleter = std::bind(&QuadTreeSceneData::shouldDeleteSprite, data.get(), std::placeholders::_1);
    detachChild(deleter);
}

void QuadTreeScene::onUpdateMyScene(float deltaTime)
{
    (void)deltaTime;
}

void QuadTreeScene::addConllisionGroupID(int groupID)
{
    data->conllisionPair.push_back(groupID);
}
